package moe.cnkirito.security.oauth2.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.util.Arrays;

/**
 * @author 徐靖峰
 * Date 2018-04-19
 */
@Configuration//配置服务器
public class OAuth2ServerConfig {

    private static final String DEMO_RESOURCE_ID = "order";

    @Configuration
    @EnableResourceServer//表示开启资源服务器 资源服务器就是用来存放用户的资源，例如你在微信上的图像、openid 等信息，用户从授权服务器上拿到 access_token 之后，接下来就可以通过 access_token 来资源服务器请求数据。
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        TokenStore tokenStore;

        @Override//配置资源 ID 和 TokenStore
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(DEMO_RESOURCE_ID).stateless(true)
                    .tokenStore(tokenStore);//
        }

        @Override//资源的拦截规则
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/order/**").authenticated()
            .antMatchers("/webjars/**",
                    "/resources/**",
                    "/swagger-ui.html",
                    "/swagger-resources/**",
                    "/v2/api-docs")
                    .permitAll()
            ;//配置order访问控制，必须认证过后才可以访问

        }
    }


    @Configuration
    @EnableAuthorizationServer//表示开启授权服务器的自动化配置
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        AuthenticationManager authenticationManager;
        @Autowired
        RedisConnectionFactory redisConnectionFactory;


        @Override//用来配置客户端的详细信息 配置了客户端的 id，secret、资源 id、授权类型、授权范围以及重定向 uri
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

//        password 方案一：明文存储，用于测试，不能用于生产
//        String finalSecret = "123456";
//        password 方案二：用 BCrypt 对密码编码
//        String finalSecret = new BCryptPasswordEncoder().encode("123456");
            // password 方案三：支持多种编码，通过密码的前缀区分编码方式
            String finalSecret = "{bcrypt}"+new BCryptPasswordEncoder().encode("123456");
            //配置两个客户端,一个用于password认证一个用于client认证
            clients.inMemory().withClient("client_1")
                    .resourceIds(DEMO_RESOURCE_ID)
                    .authorizedGrantTypes("client_credentials", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret)
                    .and().withClient("client_2")
                    .resourceIds(DEMO_RESOURCE_ID)
                    .authorizedGrantTypes("password", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret);
        }

        @Override//用来配置令牌的访问端点和令牌服务
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.authenticationManager(authenticationManager)
                    .tokenStore(tokenStore())
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

//            endpoints
//                    .tokenStore(new RedisTokenStore(redisConnectionFactory))
//                    .authenticationManager(authenticationManager)
//                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

           //??? endpoints.reuseRefreshTokens(true);
        }

//        @Qualifier("TokenStore")
        @Autowired
        TokenStore tokenStore;
        @Autowired
        ClientDetailsService clientDetailsService;
        @Autowired
        JwtAccessTokenConverter jwtAccessTokenConverter;

        @Bean//JwtTokenStore（无状态登录，服务端不需要保存信息）
        TokenStore tokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        @Bean//可以实现将用户信息和 JWT 进行转换（将用户信息转为 jwt 字符串，或者从 jwt 字符串提取出用户信息）
        JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey("javaboy");
            return converter;
        }
        @Bean
        AuthorizationServerTokenServices tokenServices() {
            DefaultTokenServices services = new DefaultTokenServices();
            services.setClientDetailsService(clientDetailsService);
            services.setSupportRefreshToken(true);//Token 是否支持刷新、
            services.setTokenStore(tokenStore);//Token 的存储位置、
            services.setAccessTokenValiditySeconds(60 * 60 * 24 * 2);//Token 的有效期以及
            services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//refresh_token 的有效期
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
            services.setTokenEnhancer(tokenEnhancerChain);
            return services;
        }


        @Override//用来配置令牌端点的安全约束，也就是这个端点谁能访问，谁不能访问
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            //允许表单认证
            oauthServer.allowFormAuthenticationForClients();
        }

    }

}
